#include "syber_popen.h"
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>
#include <zlog.h>

#define SHELL "/bin/sh"

static pid_t *childpid = NULL; /* ptr to array allocated at run-time */
static int maxfd       = 512;  /* from our open_max(), {Prog openmax} */
static pthread_mutex_t childpid_init_lock = PTHREAD_MUTEX_INITIALIZER;

#if 1

FILE *syber_popen(char *command, char *type)
{
    int pfd[2];
    pid_t pid;
    FILE *fp;
    /* only allow "r" or "w" */
    if ((type[0] != 'r' && type[0] != 'w') || type[1] != 0) {
        errno = EINVAL; /* required by POSIX.2 */
        return (NULL);
    }

    if (childpid == NULL) {
        while (pthread_mutex_trylock(&childpid_init_lock) != 0);

        if(childpid == NULL) {
            struct rlimit limit;
            if (getrlimit(RLIMIT_NOFILE, &limit) == -1)
                return (NULL);

            maxfd = (int)limit.rlim_cur;
            dzlog_info("support max fd count is %d", maxfd);
            if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
                return (NULL);

            for(int i = 0; i < maxfd; ++i) {
                childpid[i] = -1;
            }

        }
        pthread_mutex_unlock (&childpid_init_lock);
    }

    if (pipe(pfd) < 0)
        return (NULL); /* errno set by pipe() */

    if ((pid = fork()) < 0) {
        dzlog_error("error %i", pid);
        return (NULL);   /* errno set by fork() */
    }
    else if (pid == 0) { /* child */
        if (*type == 'r') {
            close(pfd[0]);
            if (pfd[1] != STDOUT_FILENO) {
                dup2(pfd[1], STDOUT_FILENO);
                close(pfd[1]);
            }
        } else {
            close(pfd[1]);
            if (pfd[0] != STDIN_FILENO) {
                dup2(pfd[0], STDIN_FILENO);
                close(pfd[0]);
            }
        }
        execl(SHELL, "sh", "-c", command, (char *)0);
        _exit(127);
    }
    /* parent */
    if (*type == 'r') {
        close(pfd[1]);
        if ((fp = fdopen(pfd[0], type)) == NULL)
            return (NULL);
    } else {
        close(pfd[0]);
        if ((fp = fdopen(pfd[1], type)) == NULL)
            return (NULL);
    }

    const int file_number = fileno(fp);
    const int old_value = childpid[file_number];
    childpid[file_number] = pid;

    if(old_value != -1)
    {
        printf("we are overwritting file value %i %i", file_number, old_value);
    }

    return (fp);
}

int syber_pclose(FILE *fp, int flag)
{
    int fd, stat = -1;
    pid_t pid;

    if (childpid == NULL) {
        return (-1); /* popen() has never been called */
    }

    fd = fileno(fp);
    if ((pid = childpid[fd]) == -1) {
        dzlog_info("Unable to find PID");
        return (-1); /* fp wasn't opened by popen() */
    }

    childpid[fd] = -1;
    if (fclose(fp) == EOF) {
        return (-1);
    }

    while (pid && waitpid(pid, &stat, 0) == -1) {

        if (errno != EINTR) {
            dzlog_error("errno on wait pid isn't EINTR or 4 it is %i", errno);
            return (-1);
        }

        // If wait pid didn't kill the process we kill it with sigkill
        int kill_results = kill(pid, SIGKILL);
        if (kill_results == -1) {
            dzlog_error("Unable to kill the PID with error %i", errno);
        }

        usleep(1000);
    }

    return (stat);
}

#else

static struct pid {
    struct pid *next;
    FILE *fp;
    pid_t pid;
} * pidlist;

extern char **environ;

FILE *syber_popen(char *command, char *type)
{
    struct pid *volatile cur;
    FILE *iop   = NULL;
    int pdes[2] = {0};
    pid_t pid;
    char *argp[] = {"sh", "-c", NULL, NULL};  //调用shell

    if ((*type != 'r' && *type != 'w') || type[1] != '\0') {
        errno = EINVAL;
        return (NULL);
    }
    if ((cur = malloc(sizeof(struct pid))) == NULL)
        return (NULL);
    if (pipe(pdes) < 0) {
        free(cur);
        return (NULL);
    }

    switch (pid = fork()) {  //创建新进程
        case -1:             /* Error. */
            (void)close(pdes[0]);
            (void)close(pdes[1]);
            free(cur);
            return (NULL);
            /* NOTREACHED */
        case 0: /* Child. */
        {
            struct pid *pcur;
            /*
             * We fork()'d, we got our own copy of the list, no
             * contention.
             */
            for (pcur = pidlist; pcur; pcur = pcur->next) {
                if (pcur->fp) {
                    close(fileno(pcur->fp));
                }
            }
            if (*type == 'r') {
                (void)close(pdes[0]);
                if (pdes[1] != STDOUT_FILENO) {
                    (void)dup2(pdes[1], STDOUT_FILENO);
                    (void)close(pdes[1]);
                }
            } else {
                (void)close(pdes[1]);
                if (pdes[0] != STDIN_FILENO) {
                    (void)dup2(pdes[0], STDIN_FILENO);
                    (void)close(pdes[0]);
                }
            }
            argp[2] = (char *)command;
            execve(SHELL, argp, environ);  //执行程序
            _exit(127);
            /* NOTREACHED */
        } break;
    }

    /* Parent; assume fdopen can't fail. */
    if (*type == 'r') {
        iop = fdopen(pdes[0], type);
        (void)close(pdes[1]);
    } else {
        iop = fdopen(pdes[1], type);
        (void)close(pdes[0]);
    }
    /* Link into list of file descriptors. */
    cur->fp   = iop;
    cur->pid  = pid;
    cur->next = pidlist;
    pidlist   = cur;
    return (iop);
}

int syber_pclose(FILE *stream, int flag)
{
    struct pid *cur = NULL, *last = NULL;
    int pstat;
    pid_t pid;
    /* Find the appropriate file pointer. */
    for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next)
        if (cur->fp == stream)
            break;
    if (cur == NULL)
        return (-1);
    (void)fclose(stream);
    if (0 == flag) {  //源代码方式
        do {
            pid = waitpid(cur->pid, &pstat, 0);
        } while (pid == -1 && errno == EINTR);
    } else {  //增加方式
        if (pid != 0)
            kill(pid, SIGKILL);  //强制结束调用
    }
    /* Remove the entry from the linked list. */
    if (last == NULL)
        pidlist = cur->next;
    else
        last->next = cur->next;
    free(cur);
    return (pid == -1 ? -1 : pstat);
}
#endif
